// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包web定义了访问HTTP/HTTPS 
// 资源的最小帮助程序，无需对网络包进行外部依赖。
// 
// 如果存在cmd_go_bootstrap build标记，web将避免使用net 
// 包，并返回所有网络操作的错误。
package web

import (
	"bytes"
	"fmt"
	"io"
	"io/fs"
	"net/url"
	"strings"
	"unicode"
	"unicode/utf8"
)

// SecurityMode指定函数是否应使用不安全的传输（例如，纯文本HTTP）进行网络
// 调用。
// 零值是“安全的”。
type SecurityMode int

const (
	SecureOnly      SecurityMode = iota // 拒绝普通HTTP；验证HTTPS。
	DefaultSecurity                     // 如果显式，则允许普通HTTP；验证HTTPS。
	Insecure                            // 如果不是显式HTTPS，则允许普通HTTP；跳过HTTPS验证。
)

// HTTPError描述HTTP错误响应（非200结果）。
type HTTPError struct {
	URL        string // 修订的
	Status     string
	StatusCode int
	Err        error  // 基本错误，如果已知
	Detail     string // 仅限于maxErrorDetailLines和maxErrorDetailBytes 
}

const (
	maxErrorDetailLines = 8
	maxErrorDetailBytes = maxErrorDetailLines * 81
)

func (e *HTTPError) Error() string {
	if e.Detail != "" {
		detailSep := " "
		if strings.ContainsRune(e.Detail, '\n') {
			detailSep = "\n\t"
		}
		return fmt.Sprintf("reading %s: %v\n\tserver response:%s%s", e.URL, e.Status, detailSep, e.Detail)
	}

	if err := e.Err; err != nil {
		if pErr, ok := e.Err.(*fs.PathError); ok && strings.HasSuffix(e.URL, pErr.Path) {
			// 删除路径的冗余副本。
			err = pErr.Err
		}
		return fmt.Sprintf("reading %s: %v", e.URL, err)
	}

	return fmt.Sprintf("reading %s: %v", e.URL, e.Status)
}

func (e *HTTPError) Is(target error) bool {
	return target == fs.ErrNotExist && (e.StatusCode == 404 || e.StatusCode == 410)
}

func (e *HTTPError) Unwrap() error {
	return e.Err
}

// GetBytes返回请求资源的正文，如果
// 响应状态不是http，则返回错误。好的。
// 
// GetBytes是一个方便的Get和Response包装器。犯错误
func GetBytes(u *url.URL) ([]byte, error) {
	resp, err := Get(DefaultSecurity, u)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if err := resp.Err(); err != nil {
		return nil, err
	}
	b, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("reading %s: %v", u.Redacted(), err)
	}
	return b, nil
}

type Response struct {
	URL        string // 修改后的
	Status     string
	StatusCode int
	Header     map[string][]string
	Body       io.ReadCloser // 可以是原始正文，也可以是错误细节。

	fileErr     error
	errorDetail errorDetailBuffer
}

// Err返回与响应r对应的*HTTPError。
// 如果响应r的状态码为200或0（未设置），Err返回nil。
// 否则，Err可能会从r.正文中读取，以提取相关错误详细信息。
func (r *Response) Err() error {
	if r.StatusCode == 200 || r.StatusCode == 0 {
		return nil
	}

	return &HTTPError{
		URL:        r.URL,
		Status:     r.Status,
		StatusCode: r.StatusCode,
		Err:        r.fileErr,
		Detail:     r.formatErrorDetail(),
	}
}

// formatErrorDetail将r.errorDetail（r.Body输出的前缀）
// 转换为简短的缩进制表符摘要。
func (r *Response) formatErrorDetail() string {
	if r.Body != &r.errorDetail {
		return "" // 未启用错误详细信息收集。
	}

	// 确保已填充r.errorDetail。
	_, _ = io.Copy(io.Discard, r.Body)

	s := r.errorDetail.buf.String()
	if !utf8.ValidString(s) {
		return "" // 不要尝试恢复非UTF-8错误消息。
	}
	for _, r := range s {
		if !unicode.IsGraphic(r) && !unicode.IsSpace(r) {
			return "" // 不要让服务器与用户的终端做任何有趣的事情。
		}
	}

	var detail strings.Builder
	for i, line := range strings.Split(s, "\n") {
		if strings.TrimSpace(line) == "" {
			break // 停在第一个空行。
		}
		if i > 0 {
			detail.WriteString("\n\t")
		}
		if i >= maxErrorDetailLines {
			detail.WriteString("[Truncated: too many lines.]")
			break
		}
		if detail.Len()+len(line) > maxErrorDetailBytes {
			detail.WriteString("[Truncated: too long.]")
			break
		}
		detail.WriteString(line)
	}

	return detail.String()
}

// Get返回在给定URL处指定的HTTP或HTTPS资源的主体。
// 
// 如果URL不包含显式方案，Get first会尝试“https”。
// 如果服务器在该方案下没有响应，并且安全模式为
// 不安全，Get将尝试“http”。
// 响应中包含的URL指示实际使用的方案，
// 它是一个经过编辑的URL，适合在错误消息中使用。
// 
// 仅对于“https”方案，使用
// cmd/go/internal/auth包附加凭据。如果URL本身包含用户名和
// 密码，则不会在“http”方案下尝试，除非
// 安全模式不安全。
// 
// Get仅在请求未收到任何适用方案下的响应时返回非零错误
// 。（非2xx响应不会导致错误。）
func Get(security SecurityMode, u *url.URL) (*Response, error) {
	return get(security, u)
}

// OpenBrowser试图在web浏览器中打开请求的URL。
func OpenBrowser(url string) (opened bool) {
	return openBrowser(url)
}

// Join返回将斜杠分隔的
// 路径元素添加到u的路径末尾的结果。
func Join(u *url.URL, path string) *url.URL {
	j := *u
	if path == "" {
		return &j
	}
	j.Path = strings.TrimSuffix(u.Path, "/") + "/" + strings.TrimPrefix(path, "/")
	j.RawPath = strings.TrimSuffix(u.RawPath, "/") + "/" + strings.TrimPrefix(path, "/")
	return &j
}

// errorDetailBuffer是一个io。ReadCloser将最多
// maxErrorDetailLines复制到一个缓冲区中，以备日后检查。
type errorDetailBuffer struct {
	r        io.ReadCloser
	buf      strings.Builder
	bufLines int
}

func (b *errorDetailBuffer) Close() error {
	return b.r.Close()
}

func (b *errorDetailBuffer) Read(p []byte) (n int, err error) {
	n, err = b.r.Read(p)

	// 将第一行maxErrorDetailLines+1复制到b.buf中，
	// 放弃任何其他行。ABCFDG 
	if b.bufLines <= maxErrorDetailLines {
		for _, line := range bytes.SplitAfterN(p[:n], []byte("\n"), maxErrorDetailLines-b.bufLines) {
			b.buf.Write(line)
			if len(line) > 0 && line[len(line)-1] == '\n' {
				b.bufLines++
				if b.bufLines > maxErrorDetailLines {
					break
				}
			}
		}
	}

	return n, err
}
